﻿// <copyright file="Reflector.cs" company="Recipe Database">
//     Copyright (c) Recipe Database. All rights reserved.
// </copyright>
// <author>Bjarke Meier</author>
//-----------------------------------------------------------------------

namespace Core
{
  using System;
  using System.Diagnostics.CodeAnalysis;
  using System.Linq.Expressions;

  /// <summary>
  /// Class to help doing type safe (strongly typed) reflection.
  /// It can be used to get names of method parameters, values, methods, classes, namespaces etc.
  /// without typing strings, which may get out of sync during coding.
  /// For more information the following links may be of interest:
  /// - http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html
  /// - http://www.clariusconsulting.net/blogs/kzu/archive/2006/07/06/TypedReflection.aspx
  /// </summary>
  public static class Reflector
  {
    /// <summary>
    /// Gets the parameter name of a method. Usage e.g. <code>ReflectionHelper.GetParameterName(() => type)</code>,
    /// where <code>type</code> is the parameter of the caller of this method.
    /// </summary>
    /// <typeparam name="T">The return type of the lambda function.</typeparam>
    /// <param name="expression">An expression tree of the lambda function.</param>
    /// <returns>The name of the parameter in the body of the lambda function.</returns>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static string GetParameterName<T>(Expression<Func<T>> expression)
    {
      if (expression == null)
      {
        // TODO: Can we avoid the const string argument? (We cannot call ourself to get the name of the parameter)
        throw new ArgumentNullException("expression");
      }

      ExpressionType type;
      var name = GetName(expression, out type);
      if (type != ExpressionType.MemberAccess)
      {
        throw new ArgumentException("The argument 'expression' must be a parameter.");
      }

      return name;
    }

    /// <summary>
    /// Get the name of a variable. Note that e.g. <code>const string foo = "foo";</code> is not a variable declaration but a constant declaration.
    /// </summary>
    /// <typeparam name="T">The return type of the lambda function.</typeparam>
    /// <param name="expression">An expression tree of the lambda function.</param>
    /// <returns>The name of the variable in the body of the lambda function.</returns>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static string GetVariableName<T>(Expression<Func<T>> expression)
    {
      // TODO: Check that expression is indeed a variable and not e.g. a const or other.
      if (expression == null)
      {
        // TODO: Can we avoid the const string argument? (We cannot call ourself to get the name of the parameter)
        throw new ArgumentNullException("expression");
      }

      ExpressionType type;
      var name = GetName(expression, out type);
      if (type != ExpressionType.MemberAccess)
      {
        throw new ArgumentException("The argument 'expression' must be a variable.");
      }

      return name;
    }

    /// <summary>
    /// Private help method to evaluate an expression and get the name of the body expression.
    /// </summary>
    /// <param name="expression">lambda function expression.</param>
    /// <param name="type">The type of the body of the lambda function of the expression.</param>
    /// <returns>String representation of body of lambda function.</returns>
    private static string GetName(Expression expression, out ExpressionType type)
    {
      type = expression.NodeType;
      switch (type)
      {
        case ExpressionType.Lambda:
          var body = ((LambdaExpression)expression).Body;
          return GetName(body, out type);
        case ExpressionType.Constant:
          return expression.ToString();
        case ExpressionType.MemberAccess:
          var me = (MemberExpression)expression;
          return me.Member.Name;
        default:
          throw new NotImplementedException();
      }
    }
  }
}
